/**
 * Copyright 2018 orivil.com. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package wechat

import (
	"gitee.com/tugoer/orivil"
	"github.com/google/go-querystring/query"
	"gitee.com/tugoer/uuid"
	"encoding/json"
	"errors"
	"net/http"
)

type scope string

const (
	BaseScope scope = "snsapi_base"
	FullScope scope = "snsapi_userinfo"
)

// 从 session 中获取 user info, 如果用户未登陆, 则使用 NewRedirect() 获得授权页面地址,
// 跳转至授权页面当用户授权后 user info 将被保存于 session 中. user info 是一次性的.
func GetUserInfo(ctx *orivil.Context) (info *UserInfo) {
	info, _ = ctx.Session().MFlash("user-info").(*UserInfo)
	return
}

// 从 session 中获取 access_token, 如果未获得, 则使用 NewRedirect() 获得授权页面地址,
// 跳转至授权页面后 access token 将被保存于 session 中. access token 是一次性的.
func GetAuthAccessToken(ctx *orivil.Context) (token *AuthAccessToken) {
	token, _ = ctx.Session().MFlash("auth-access-token").(*AuthAccessToken)
	return
}

// 中间件用于代理公众号获取 access_token 或用户信息
var AuthMiddleware orivil.BeforeHandler = func(ctx *orivil.Context) {
	state := ctx.Query().Get("state")
	if state != "" {
		if _state, ok := ctx.Session().MFlash("state").(string); ok && state == _state {
			// 授权代理公众号
			app := GetAppEnv(ctx)
			u, _ := query.Values(&accessConfig{
				AppID:     app.AppID,
				Secret:    app.Secret,
				Code:      ctx.Query().Get("code"),
				GrantType: "authorization_code",
			})
			// 获得 access token, 可获得 openid, 不需要用户授权
			access := &AuthAccessToken{}
			res, err := http.Get("https://api.weixin.qq.com/sns/oauth2/access_token?" + u.Encode())
			ctx.ErrWarning(err)
			decoder := json.NewDecoder(res.Body)
			err = decoder.Decode(access)
			ctx.ErrWarning(err)
			res.Body.Close()
			// 将 access_token 保存于 session 中
			ctx.Session().MSet("auth-access-token", access)
			s := ctx.Session().MGet("scope").(scope)
			if s == FullScope {
				// 获得 user info,  需要用户授权
				res, err = http.Get("https://api.weixin.qq.com/sns/userinfo?access_token=" + access.AccessToken + "&openid=" + access.Openid + "&lang=zh_CN")
				ctx.ErrWarning(err)
				decoder = json.NewDecoder(res.Body)
				user := &UserInfo{}
				err = decoder.Decode(user)
				ctx.ErrWarning(err)
				res.Body.Close()
				// 将 user info 保存于 session 中
				ctx.Session().MSet("user-info", user)
			}
			// 跳转至授权之前的页面
			ctx.Redirect(ctx.Session().MGet("referer").(string))
		} else {
			ctx.ErrEmergency(errors.New("state 码错误"))
		}
	}
}

// 跳转至授权页面，登陆成功后跳回当前页面
func NewRedirect(ctx *orivil.Context, s scope, app *Env) string {
	state := uuid.New()
	ctx.Session().MSet("scope", s)
	ctx.Session().MSet("state", state)
	ctx.Session().MSet("referer", ctx.Header().Get("Referer"))
	u, _ := query.Values(&redirectConfig {
		AppID:        app.AppID,
		// 将授权信息发送至 app 主页
		RedirectUri:  app.Host(),
		ResponseType: "code",
		Scope:        string(s),
		State:        state,
	})
	return "https://open.weixin.qq.com/connect/oauth2/authorize?" + u.Encode() + "#wechat_redirect"
}

//// AuthMiddleware 需要添加到根路径
//var AuthMiddleware orivil.BeforeHandler = func(ctx *orivil.Context) {
//	state := ctx.Query().Get("state")
//	if str, ok := ctx.Session().MFlash("state").(string); ok && str == state {
//		code := ctx.Query().Get("code")
//		app := GetAppEnv(ctx)
//		u, _ := query.Values(&accessConfig {
//			AppID:     app.AppID,
//			Secret:    app.Secret,
//			Code:      code,
//			GrantType: "authorization_code",
//		})
//		// 获得 access token
//		access := &accessToken{}
//		res, err := http.Get("https://api.weixin.qq.com/sns/oauth2/access_token?" + u.Encode())
//		ctx.ErrWarning(err)
//		decoder := json.NewDecoder(res.Body)
//		err = decoder.Decode(access)
//		ctx.ErrWarning(err)
//		res.Body.Close()
//		ctx.Session().MSet("access_token", access)
//		// 获得 user info
//		res, err = http.Get("https://api.weixin.qq.com/sns/userinfo?access_token=" + access.AccessToken + "&openid=" + access.Openid + "&lang=zh_CN")
//		ctx.ErrWarning(err)
//		decoder = json.NewDecoder(res.Body)
//		user := &UserInfo{}
//		err = decoder.Decode(user)
//		ctx.ErrWarning(err)
//		res.Body.Close()
//		ctx.Session().MSet("user-info", user)
//	} else {
//		ctx.ErrEmergency(errors.New("state 码错误"))
//	}
//}

type redirectConfig struct {
	AppID        string `url:"appid"`
	RedirectUri  string `url:"redirect_uri"`
	ResponseType string `url:"response_type"`
	Scope        string `url:"scope"`
	State        string `url:"state"`
}

type accessConfig struct {
	AppID     string `url:"appid"`
	Secret    string `url:"secret"`
	Code      string `url:"code"`
	GrantType string `url:"grant_type"`
}

type AuthAccessToken struct {
	AccessToken  string `json:"access_token"`
	ExpiresIn    int `json:"expires_in"`
	RefreshToken string `json:"refresh_token"`
	Openid       string `json:"openid"`
	Scope        string `json:"scope"`
}

type UserInfo struct {
	Openid     string   `json:"openid"`
	Nickname   string   `json:"nickname"`
	Sex        int   `json:"sex"`
	Province   string   `json:"province"`
	City       string   `json:"city"`
	Country    string   `json:"country"`
	HeadImgUrl string   `json:"headimgurl"`
	Privilege  []string `json:"privilege"`
	UnionID    string   `json:"unionid"`
}
